﻿/*
 * Created by SharpDevelop.
 * User: sanmadmin
 * Date: 6/17/2011
 * Time: 2:46 AM
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Threading;
using System.Net.Security;
using System.Text;
using System.Net;
using System.Reflection;

namespace RTMP
{
    /// <summary>
    /// Description of NetConnection.
    /// </summary>
    public class NetConnection
    {
        public static readonly byte[] DH_MODULUS_BYTES = new byte[128] 
        { 
            0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74,
            0x02, 0x0B, 0xBE, 0xA6, 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, 0xF2, 0x5F, 0x14, 0x37, 
            0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, 
            0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
        };
        public bool IsConnected = false;
        public Link link = new Link();

        int outChunkSize = Coding.RTMP_DEFAULT_CHUNKSIZE;
        int bytesReadTotal = 0;
        int lastSentBytesRead = 0;
        int receiveTimeoutMS = Coding.TIMEOUT_RECEIVE; // intial timeout until stream is connected (don't set too low, as the server's answer after the handshake might take some time)
        int m_nClientBW = 2500000;
        RTMPPacket[] m_vecChannelsIn = new RTMPPacket[Coding.RTMP_CHANNELS];
        RTMPPacket[] m_vecChannelsOut = new RTMPPacket[Coding.RTMP_CHANNELS];
        uint[] m_channelTimestamp = new uint[Coding.RTMP_CHANNELS]; // abs timestamp of last packet
        int m_numInvokes;
        int m_nBWCheckCounter;
        int m_nServerBW;
        byte m_nClientBW2;
        int m_nBufferMS = (10 * 60 * 60 * 1000)	/* 10 hours default */;
        //Queue<invokePacket> m_methodCalls = new Queue<invokePacket>(); //remote method calls queue
        private BaseSocket m_socket = null;
        private int InChunkSize { get; set; }
        public object client = null;
        public bool bFP9Handshake = true;
        // private delegate void ResultHandler(object[] paramlist);
        //public void delegate 
        public Logger logger = new Logger();

        internal Dictionary<string, SharedObject> SOList = new Dictionary<string, SharedObject>();
        internal Dictionary<int, NetStream> NSList = new Dictionary<int, NetStream>();
        internal Dictionary<string, NetStream> NSPendings = new Dictionary<string, NetStream>();
        private Dictionary<int, invokePacket> m_methodCalls = new Dictionary<int, invokePacket>();

        public delegate void StatusHandler(object props);
        public delegate void ResultHandler(string method, object result);
        public delegate void ResponseHandler(object props);
        public event StatusHandler Status;
        public event ResultHandler Result;

        private Org.BouncyCastle.Crypto.IStreamCipher rc4In = null;
        private Org.BouncyCastle.Crypto.IStreamCipher rc4Out = null;
        private Org.BouncyCastle.Crypto.Agreement.DHBasicAgreement keyAgreement;

        public NetConnection()
        {
        }
        ~NetConnection()
        {
            try { Close(); }
            catch { }
        }
        public bool Connect(string url, object[] paramlist = null)
        {
            Uri uri = new Uri(url);
            link.FromRtmpUrl(uri);
            if (link.swfUrl != null)
                link.GetSwf();
            link.bLiveStream = true;
            if (paramlist != null && paramlist.Length > 0)
            {
                link.extras = new AMFObject();
                foreach (object obj in paramlist)
                {
                    AMFObjectProperty prop = new AMFObjectProperty();
                    if (obj.GetType() == typeof(string))
                    {
                        prop.m_strVal = (string)obj;
                        prop.m_type = AMFDataType.AMF_STRING;
                    }
                    else if (obj.GetType() == typeof(int))
                    {
                        prop.m_dNumVal = (int)obj;
                        prop.m_type = AMFDataType.AMF_NUMBER;
                    }
                    else if (obj.GetType() == typeof(bool))
                    {
                        prop.m_dNumVal = (double)obj;
                        prop.m_type = AMFDataType.AMF_BOOLEAN;
                    }
                    else continue;
                    link.extras.AddProperty(prop);
                }
            }
            Close();
            m_socket = new BaseSocket();
            m_socket.ConnectReady += new BaseSocket.ConnectReadyDelegate(m_socket_ConnectReady);
            m_socket.BaseSocketError += new BaseSocket.BaseSocketErrorDelegate(m_socket_BaseSocketError);
            m_socket.BaseSocketObjectError += new BaseSocket.BaseSocketObjectErrorDelegate(m_socket_BaseSocketObjectError);
            m_socket.ReceiveReady += new BaseSocket.ReceiveReadyDelegate(m_socket_ReceiveReady);
            m_socket.m_IP = link.hostname;
            m_socket.m_PORT = link.port;
            m_socket.Connect();
            return true;
        }
        private void m_socket_ConnectReady(object sender, ConnectReadyArgs e)
        {
            ProcessStart();
        }
        private void m_socket_BaseSocketError(object sender, SocketException e)
        {
            logger.Log("Socket Object error: " + e.Message);
        }
        private void m_socket_BaseSocketObjectError(object sender, BaseSocketObjectErrorArgs e)
        {
            logger.Log("Socket error: " + e.message);
        }

        private void ProcessStart()
        {
            if (!HandShake(bFP9Handshake))
                return;
            if (!SendConnect())
                return;
            logger.Log("Requests processing starts");
            m_socket.Receive(1, ProcessRequest);
        }

        void m_socket_ReceiveReady(object sender, ReceiveReadyArgs e)
        {
        }
        private void ProcessRequest(byte type)
        {
            if (m_socket == null || !m_socket.bConnected)
                return;
            byte[] typebuffer = new byte[1];
            typebuffer[0] = type;
            try
            {
                RTMPPacket packet = null;
                if (rc4In != null)
                    rc4In.ProcessBytes(typebuffer, 0, 1, typebuffer, 0);
                if (!ReadPacket(out packet, typebuffer[0]))
                {
                    if (m_socket == null || !m_socket.bConnected)
                        return;
                    throw new Exception("Receive error in ReadPacket()");
                }
                while (m_socket != null && m_socket.bConnected && !packet.IsReady())
                {
                    byte[] buffer = new byte[1];
                    if (m_socket.m_socket.Receive(buffer) > 0)
                    {
                        if (rc4In != null)
                            rc4In.ProcessBytes(buffer, 0, 1, buffer, 0);
                        if (!ReadPacket(out packet, buffer[0]))
                        {
                            if (m_socket == null || !m_socket.bConnected)
                                return;
                            throw new Exception("Receive error in ReadPacket()1");
                        }
                    }
                    else
                        break;
                }
                if (m_socket == null || !m_socket.bConnected)
                    return;
                HandleRequest(packet);
            }
            catch (Exception ex)
            {
                logger.Log("Receive error: " + ex.Message);
            }
            finally
            {
                if (m_socket != null && m_socket.bConnected)
                    m_socket.Receive(1, ProcessRequest);
            }
        }
        int HandleRequest(RTMPPacket packet)
        {

            switch (packet.PacketType)
            {
                case PacketType.ChunkSize:
                    HandleChangeChunkSize(packet);
                    break;
                case PacketType.BytesRead:
                    //CLog::Log(LOGDEBUG,"%s, received: bytes read report", __FUNCTION__);
                    break;
                case PacketType.Control:
                    HandlePing(packet);
                    break;
                case PacketType.ServerBW:
                    HandleServerBW(packet);
                    break;
                case PacketType.ClientBW:
                    HandleClientBW(packet);
                    break;
                case PacketType.Audio:
                //CLog::Log(LOGDEBUG,"%s, received: audio %lu bytes", __FUNCTION__, packet.m_nBodySize);
                //HandleAudio(packet);
                //if (m_mediaChannel == 0) m_mediaChannel = packet.m_nChannel;
                //if (Pausing == 0) m_mediaStamp = packet.m_nTimeStamp;                    
                case PacketType.Video:
                    //CLog::Log(LOGDEBUG,"%s, received: video %lu bytes", __FUNCTION__, packet.m_nBodySize);
                    //HandleVideo(packet);
                    // if (m_mediaChannel == 0) m_mediaChannel = packet.m_nChannel;
                    //if (!posed) timeStamp = packet.m_nTimeStamp;	
                    if (NSList.ContainsKey(packet.m_nInfoField2))
                        NSList[packet.m_nInfoField2].handlePacket(packet);
                    break;
                case PacketType.Metadata:
                    //CLog::Log(LOGDEBUG,"%s, received: notify %lu bytes", __FUNCTION__, packet.m_nBodySize);
                    HandleMetadata(packet);
                    break;
                case PacketType.Invoke:
                    //CLog::Log(LOGDEBUG,"%s, received: invoke %lu bytes", __FUNCTION__, packet.m_nBodySize);
                    HandleInvoke(packet);
                    break;
                case PacketType.FlvTags:
                    //logger.Log(string.Format("received: FLV tag(s) {0} bytes", packet.m_nBodySize));
                    //HandleFlvTags(packet);
                    break;
                case PacketType.SharedObject:
                    HandleSharedObject(packet);
                    break;
                default:
                    logger.Log(string.Format("Ignoring packet of type {0}", packet.PacketType));
                    break;
            }
            return 1;
        }
        #region Send Client Packets
        public void Close()
        {
            foreach (NetStream strm in NSList.Values)
                strm.close();
            NSList.Clear();
            NSPendings.Clear();
            SOList.Clear();
            m_methodCalls.Clear();
            receiveTimeoutMS = Coding.TIMEOUT_RECEIVE;

            InChunkSize = Coding.RTMP_DEFAULT_CHUNKSIZE;
            outChunkSize = Coding.RTMP_DEFAULT_CHUNKSIZE;
            m_nBWCheckCounter = 0;
            m_nClientBW = 2500000;
            m_nClientBW2 = 2;
            m_nServerBW = 2500000;
            bytesReadTotal = 0;
            lastSentBytesRead = 0;
            m_numInvokes = 0;

            for (int i = 0; i < Coding.RTMP_CHANNELS; i++)
            {
                m_vecChannelsIn[i] = null;
                m_vecChannelsOut[i] = null;
                m_channelTimestamp[i] = 0;
            }
            m_methodCalls.Clear();
            if (m_socket != null && m_socket.bConnected) m_socket.close();
        }
        bool SendConnect()
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x03;   // control channel (invoke)
            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.Invoke;
            packet.AllocPacket(4096);
            logger.Log("Sending connect");
            List<byte> enc = new List<byte>();
            Coding.EncodeString(enc, "connect");
            Coding.EncodeNumber(enc, ++m_numInvokes);
            enc.Add(0x03); //Object Datatype                
            Coding.EncodeString(enc, "app", link.app); logger.Log(string.Format("app : {0}", link.app));
            if (String.IsNullOrEmpty(link.flashVer))
                Coding.EncodeString(enc, "flashVer", "WIN 10,0,32,18");
            else
                Coding.EncodeString(enc, "flashVer", link.flashVer);
            if (!string.IsNullOrEmpty(link.swfUrl)) Coding.EncodeString(enc, "swfUrl", link.swfUrl);
            Coding.EncodeString(enc, "tcUrl", link.tcUrl); logger.Log(string.Format("tcUrl : {0}", link.tcUrl));
            Coding.EncodeBoolean(enc, "fpad", false);
            Coding.EncodeNumber(enc, "capabilities", 15.0);
            Coding.EncodeNumber(enc, "audioCodecs", 3191.0);
            Coding.EncodeNumber(enc, "videoCodecs", 252.0);
            Coding.EncodeNumber(enc, "videoFunction", 1.0);
            if (!string.IsNullOrEmpty(link.pageUrl)) Coding.EncodeString(enc, "pageUrl", link.pageUrl);
            if (link.objectEncoding != 0) Coding.EncodeNumber(enc, "objectEncodeing", link.objectEncoding);
            enc.Add(0); enc.Add(0); enc.Add(0x09); // end of object - 0x00 0x00 0x09

            // add auth string
            if (!string.IsNullOrEmpty(link.auth))
            {
                Coding.EncodeBoolean(enc, true);
                Coding.EncodeString(enc, link.auth);
            }

            // add aditional arbitrary AMF connection properties
            if (link.extras != null)
            {
                foreach (AMFObjectProperty aProp in link.extras.m_properties) aProp.Encode(enc);
            }

            Array.Copy(enc.ToArray(), packet.m_body, enc.Count);
            packet.m_nBodySize = (uint)enc.Count;

            return SendPacket(packet);
        }

        private bool SendRequestData(string id, string request)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x03;   // control channel (invoke)
            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.Invoke_AMF3;

            List<byte> enc = new List<byte>();
            enc.Add(0x00);
            Coding.EncodeString(enc, "requestData");
            Coding.EncodeNumber(enc, 0);
            enc.Add(0x05); // NULL  
            Coding.EncodeString(enc, id);
            Coding.EncodeString(enc, request);

            packet.m_body = enc.ToArray();
            packet.m_nBodySize = (uint)enc.Count;

            logger.Log(string.Format("Sending requestData: ({0},{1})", id, request));

            return SendPacket(packet);
        }

        /// <summary>
        /// The type of Ping packet is 0x4 and contains two mandatory parameters and two optional parameters. 
        /// The first parameter is the type of Ping (short integer).
        /// The second parameter is the target of the ping. 
        /// As Ping is always sent in Channel 2 (control channel) and the target object in RTMP header is always 0 
        /// which means the Connection object, 
        /// it's necessary to put an extra parameter to indicate the exact target object the Ping is sent to. 
        /// The second parameter takes this responsibility. 
        /// The value has the same meaning as the target object field in RTMP header. 
        /// (The second value could also be used as other purposes, like RTT Ping/Pong. It is used as the timestamp.) 
        /// The third and fourth parameters are optional and could be looked upon as the parameter of the Ping packet. 
        /// Below is an unexhausted list of Ping messages.
        /// type 0: Clear the stream. No third and fourth parameters. The second parameter could be 0. After the connection is established, a Ping 0,0 will be sent from server to client. The message will also be sent to client on the start of Play and in response of a Seek or Pause/Resume request. This Ping tells client to re-calibrate the clock with the timestamp of the next packet server sends.
        /// type 1: Tell the stream to clear the playing buffer.
        /// type 3: Buffer time of the client. The third parameter is the buffer time in millisecond.
        /// type 4: Reset a stream. Used together with type 0 in the case of VOD. Often sent before type 0.
        /// type 6: Ping the client from server. The second parameter is the current time.
        /// type 7: Pong reply from client. The second parameter is the time the server sent with his ping request.
        /// type 26: SWFVerification request
        /// type 27: SWFVerification response
        /// type 31: Buffer empty
        /// type 32: Buffer full
        /// </summary>
        /// <param name="nType"></param>
        /// <param name="nObject"></param>
        /// <param name="nTime"></param>
        /// <returns></returns>
        bool SendPing(short nType, int nObject, uint nTime)
        {
            // logger.Log(string.Format("Sending ping type: {0}", nType));

            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x02;   // control channel (ping)
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Control;
            //packet.m_nInfoField1 = System.Environment.TickCount;

            int nSize = (nType == 0x03 ? 10 : 6); // type 3 is the buffer time and requires all 3 parameters. all in all 10 bytes.
            if (nType == 0x1B) nSize = 44;
            packet.AllocPacket(nSize);
            packet.m_nBodySize = (uint)nSize;

            List<byte> buf = new List<byte>();
            Coding.EncodeInt16(buf, nType);

            if (nType == 0x1B)
            {
                buf.AddRange(link.SWFVerificationResponse);
            }
            else
            {
                if (nSize > 2)
                    Coding.EncodeInt32(buf, (int)nObject);

                if (nSize > 6)
                    Coding.EncodeInt32(buf, (int)nTime);
            }
            packet.m_body = buf.ToArray();
            return SendPacket(packet, false);
        }
        bool SendCheckBW()
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x03;   // control channel (invoke)

            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.Invoke;
            //packet.m_nInfoField1 = System.Environment.TickCount;

            logger.Log("Sending _checkbw");
            List<byte> enc = new List<byte>();
            Coding.EncodeString(enc, "_checkbw");
            Coding.EncodeNumber(enc, ++m_numInvokes);
            enc.Add(0x05); // NULL            

            packet.m_nBodySize = (uint)enc.Count;
            packet.m_body = enc.ToArray();

            // triggers _onbwcheck and eventually results in _onbwdone
            return SendPacket(packet, false);
        }

        bool SendCheckBWResult(double txn)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x03;   // control channel (invoke)
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;
            packet.m_nTimeStamp = (uint)(0x16 * m_nBWCheckCounter); // temp inc value. till we figure it out.

            packet.AllocPacket(256); // should be enough
            List<byte> enc = new List<byte>();
            Coding.EncodeString(enc, "_result");
            Coding.EncodeNumber(enc, txn);
            enc.Add(0x05); // NULL            
            Coding.EncodeNumber(enc, (double)m_nBWCheckCounter++);

            packet.m_nBodySize = (uint)enc.Count;
            packet.m_body = enc.ToArray();

            return SendPacket(packet, false);
        }

        bool SendBytesReceived()
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x02;   // control channel (invoke)
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.BytesRead;

            packet.AllocPacket(4);
            packet.m_nBodySize = 4;

            List<byte> enc = new List<byte>();
            Coding.EncodeInt32(enc, bytesReadTotal);
            packet.m_nBodySize = (uint)enc.Count;
            packet.m_body = enc.ToArray();

            lastSentBytesRead = bytesReadTotal;
            logger.Log(string.Format("Send bytes report. ({0} bytes)", bytesReadTotal));
            return SendPacket(packet, false);
        }

        bool SendServerBW()
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x02;   // control channel (invoke)
            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.ServerBW;

            packet.AllocPacket(4);
            packet.m_nBodySize = 4;

            List<byte> bytesToSend = new List<byte>();
            Coding.EncodeInt32(bytesToSend, m_nServerBW); // was hard coded : 0x001312d0
            packet.m_body = bytesToSend.ToArray();
            return SendPacket(packet, false);
        }

        bool SendSecureTokenResponse(string resp)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x03;	/* control channel (invoke) */
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;

            logger.Log(string.Format("Sending SecureTokenResponse: {0}", resp));
            List<byte> enc = new List<byte>();
            Coding.EncodeString(enc, "secureTokenResponse");
            Coding.EncodeNumber(enc, 0.0);
            enc.Add(0x05); // NULL
            Coding.EncodeString(enc, resp);

            packet.m_nBodySize = (uint)enc.Count;
            packet.m_body = enc.ToArray();

            return SendPacket(packet, false);
        }
        public bool call(string method, ResponseHandler handler, object[] paramlist = null)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x03;   // control channel (invoke)
            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.Invoke;
            packet.AllocPacket(4096);
            List<byte> enc = new List<byte>();
            Coding.EncodeString(enc, method);
            Coding.EncodeNumber(enc, ++m_numInvokes);
            enc.Add(0x05); // NULL              
            if (paramlist != null && paramlist.Length > 0)
            {
                link.extras = new AMFObject();
                foreach (object obj in paramlist)
                {
                    AMFObjectProperty prop = new AMFObjectProperty();
                    if (prop.createFromObject(obj))
                        prop.Encode(enc);
                }
            }
            packet.m_nBodySize = (uint)enc.Count;
            packet.m_body = enc.ToArray();
            return SendPacket(packet, true, handler);
        }
        internal bool SendCreateStream()
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x03;   // control channel (invoke)
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;

            logger.Log("Sending createStream");
            packet.AllocPacket(256); // should be enough
            List<byte> enc = new List<byte>();
            Coding.EncodeString(enc, "createStream");
            Coding.EncodeNumber(enc, ++m_numInvokes);
            enc.Add(0x05); // NULL

            packet.m_nBodySize = (uint)enc.Count;
            packet.m_body = enc.ToArray();

            return SendPacket(packet);
        }

        bool SendPlay(NetStream strm)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x08;   // we make 8 our stream channel
            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.Invoke;
            packet.m_nInfoField2 = strm.ID;

            packet.AllocPacket(256); // should be enough
            List<byte> enc = new List<byte>();

            Coding.EncodeString(enc, "play");
            Coding.EncodeNumber(enc, ++m_numInvokes);
            enc.Add(0x05); // NULL              

            Coding.EncodeString(enc, strm.name);

            /* Optional parameters start and len.
             *
             * start: -2, -1, 0, positive number
             *  -2: looks for a live stream, then a recorded stream, if not found any open a live stream
             *  -1: plays a live stream
             * >=0: plays a recorded streams from 'start' milliseconds
            */
            //link.bLiveStream=true;
            if (strm.live)
            {
                Coding.EncodeNumber(enc, -1000.0d);
            }
            else
            {
                if (link.seekTime > 0.0)
                    Coding.EncodeNumber(enc, link.seekTime);
                else
                    Coding.EncodeNumber(enc, 0.0d);
            }
            /* len: -1, 0, positive number
             *  -1: plays live or recorded stream to the end (default)
             *   0: plays a frame 'start' ms away from the beginning
             *  >0: plays a live or recoded stream for 'len' milliseconds
             */

            packet.m_body = enc.ToArray();
            packet.m_nBodySize = (uint)enc.Count;

            //Logger.Log(string.Format("Sending play: '{0}' from time: '{1}'", link.playpath, m_channelTimestamp[m_mediaChannel]));

            return SendPacket(packet, false);
        }
        bool SendPause(bool doPause)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x08;   // we make 8 our stream channel
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;

            List<byte> enc = new List<byte>();

            Coding.EncodeString(enc, "pause");
            Coding.EncodeNumber(enc, ++m_numInvokes);
            enc.Add(0x05); // NULL  
            Coding.EncodeBoolean(enc, doPause);
            Coding.EncodeNumber(enc, (double)m_channelTimestamp[packet.m_nChannel]);

            packet.m_body = enc.ToArray();
            packet.m_nBodySize = (uint)enc.Count;

            // logger.Log(string.Format("Sending pause: ({0}), Time = {1}", doPause.ToString(), m_channelTimestamp[packet.m_nChannel]));

            return SendPacket(packet);
        }
        internal bool SendStop(NetStream strm)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x08;   // we make 8 our stream channel
            packet.HeaderType = HeaderType.Large;
            packet.PacketType = PacketType.Invoke;
            packet.m_nInfoField2 = strm.ID;

            packet.AllocPacket(256); // should be enough
            List<byte> enc = new List<byte>();

            Coding.EncodeString(enc, "stop");
            Coding.EncodeNumber(enc, ++m_numInvokes);
            enc.Add(0x05); // NULL              

            Coding.EncodeString(enc, strm.name);

            /* Optional parameters start and len.
             *
             * start: -2, -1, 0, positive number
             *  -2: looks for a live stream, then a recorded stream, if not found any open a live stream
             *  -1: plays a live stream
             * >=0: plays a recorded streams from 'start' milliseconds
            */
            //link.bLiveStream=true;
            if (strm.live)
            {
                Coding.EncodeNumber(enc, -1000.0d);
            }
            else
            {
                if (link.seekTime > 0.0)
                    Coding.EncodeNumber(enc, link.seekTime);
                else
                    Coding.EncodeNumber(enc, 0.0d);
            }
            /* len: -1, 0, positive number
             *  -1: plays live or recorded stream to the end (default)
             *   0: plays a frame 'start' ms away from the beginning
             *  >0: plays a live or recoded stream for 'len' milliseconds
             */

            packet.m_body = enc.ToArray();
            packet.m_nBodySize = (uint)enc.Count;

            //Logger.Log(string.Format("Sending play: '{0}' from time: '{1}'", link.playpath, m_channelTimestamp[m_mediaChannel]));

            return SendPacket(packet, false);
        }

        bool SendFCSubscribe(string subscribepath)
        {
            RTMPPacket packet = new RTMPPacket();
            packet.m_nChannel = 0x03;   // control channel (invoke)
            packet.HeaderType = HeaderType.Medium;
            packet.PacketType = PacketType.Invoke;

            logger.Log(string.Format("Sending FCSubscribe: {0}", subscribepath));
            List<byte> enc = new List<byte>();
            Coding.EncodeString(enc, "FCSubscribe");
            Coding.EncodeNumber(enc, ++m_numInvokes);
            enc.Add(0x05); // NULL
            Coding.EncodeString(enc, subscribepath);

            packet.m_nBodySize = (uint)enc.Count;
            packet.m_body = enc.ToArray();

            return SendPacket(packet);
        }

        internal bool SendPacket(RTMPPacket packet, bool queue = true, ResponseHandler handler = null)
        {
            uint last = 0;
            uint t = 0;

            RTMPPacket prevPacket = m_vecChannelsOut[packet.m_nChannel];
            if (packet.HeaderType != HeaderType.Large && prevPacket != null)
            {
                // compress a bit by using the prev packet's attributes
                if (prevPacket.m_nBodySize == packet.m_nBodySize &&
                    prevPacket.PacketType == packet.PacketType &&
                    packet.HeaderType == HeaderType.Medium)
                    packet.HeaderType = HeaderType.Small;

                if (prevPacket.m_nTimeStamp == packet.m_nTimeStamp &&
                    packet.HeaderType == HeaderType.Small)
                    packet.HeaderType = HeaderType.Minimum;

                last = prevPacket.m_nTimeStamp;
            }

            uint nSize = Coding.packetSize[(byte)packet.HeaderType];
            if (packet.m_nTimeStamp < last)
                t = last;
            else
                t = packet.m_nTimeStamp - last;
            List<byte> header = new List<byte>();//byte[RTMP_LARGE_HEADER_SIZE];
            byte c = (byte)(((byte)packet.HeaderType << 6) | packet.m_nChannel);
            header.Add(c);
            if (nSize > 1)
                Coding.EncodeInt24(header, (int)t);

            if (nSize > 4)
            {
                Coding.EncodeInt24(header, (int)packet.m_nBodySize);
                header.Add((byte)packet.PacketType);
            }

            if (nSize > 8)
                Coding.EncodeInt32LE(header, packet.m_nInfoField2);

            uint hSize = nSize;
            byte[] headerBuffer = header.ToArray();
            nSize = packet.m_nBodySize;
            byte[] buffer = packet.m_body;
            uint bufferOffset = 0;
            uint nChunkSize = (uint)outChunkSize;
            while (nSize + hSize > 0)
            {
                if (nSize < nChunkSize) nChunkSize = nSize;

                if (hSize > 0)
                {
                    byte[] combinedBuffer = new byte[headerBuffer.Length + nChunkSize];
                    Array.Copy(headerBuffer, combinedBuffer, headerBuffer.Length);
                    Array.Copy(buffer, (int)bufferOffset, combinedBuffer, headerBuffer.Length, (int)nChunkSize);
                    WriteN(combinedBuffer, 0, combinedBuffer.Length);
                    hSize = 0;
                }
                else
                {
                    WriteN(buffer, (int)bufferOffset, (int)nChunkSize);
                }

                nSize -= nChunkSize;
                bufferOffset += nChunkSize;

                if (nSize > 0)
                {
                    byte sep = (byte)(0xc0 | c);
                    hSize = 1;
                    headerBuffer = new byte[1] { sep };
                }
            }

            if (packet.PacketType == PacketType.Invoke && queue) // we invoked a remote method, keep it in call queue till result arrives
            {
                //m_methodCalls.Enqueue(new invokePacket(Coding.ReadString(packet.m_body, 1), handler));
                m_methodCalls.Add(m_numInvokes, new invokePacket(Coding.ReadString(packet.m_body, 1), handler));
            }

            m_vecChannelsOut[packet.m_nChannel] = packet;
            //m_vecChannelsOut[packet.m_nChannel].m_body = null;

            return true;
        }

        #endregion

        #region Handle Server Packets

        void HandleChangeChunkSize(RTMPPacket packet)
        {
            if (packet.m_nBodySize >= 4)
            {
                InChunkSize = Coding.ReadInt32(packet.m_body, 0);
                logger.Log(string.Format("received: chunk size change to {0}", InChunkSize));
            }
        }

        void HandlePing(RTMPPacket packet)
        {
            short nType = -1;
            if (packet.m_body != null && packet.m_nBodySize >= 2)
                nType = (short)Coding.ReadInt16(packet.m_body, 0);

            //logger.Log(string.Format("received: ping, type: {0}", nType));

            if (packet.m_nBodySize >= 6)
            {
                int nTime = Coding.ReadInt32(packet.m_body, 2);
                switch (nType)
                {
                    case 0:
                        logger.Log(string.Format("Stream Begin {0}", nTime));
                        break;
                    case 1:
                        logger.Log(string.Format("Stream EOF {0}", nTime));
                        //if (Pausing == 1) Pausing = 2;
                        break;
                    case 2:
                        logger.Log(string.Format("Stream Dry {0}", nTime));
                        break;
                    case 4:
                        logger.Log(string.Format("Stream IsRecorded {0}", nTime));
                        break;
                    case 6:
                        // server ping. reply with pong.
                        // logger.Log(string.Format("Ping {0}", nTime));
                        SendPing(0x07, nTime, 0);
                        break;
                    case 31:
                        //logger.Log(string.Format("Stream BufferEmpty {0}", nTime));
                        /*if (!Link.bLiveStream)
                        {
                            if (Pausing == 0)
                            {
                                SendPause(true);
                                Pausing = 1;
                            }
                            else if (Pausing == 2)
                            {
                                SendPause(false);
                                Pausing = 3;
                            }
                        }*/
                        break;
                    case 32:
                        //logger.Log(string.Format("Stream BufferReady {0}", nTime));
                        break;
                    default:
                        logger.Log(string.Format("Stream xx {0}", nTime));
                        break;
                }
            }

            if (nType == 0x1A)
            {
                // respond with HMAC SHA256 of decompressed SWF, key is the 30byte player key, also the last 30 bytes of the server handshake are applied
                if (link.SWFHash != null)
                {
                    SendPing(0x1B, 0, 0);
                }
                else
                {
                    logger.Log("Ignoring SWFVerification request, swfhash and swfsize parameters not set!");
                }
            }
        }

        void HandleServerBW(RTMPPacket packet)
        {
            m_nServerBW = Coding.ReadInt32(packet.m_body, 0);
            logger.Log(string.Format("HandleServerBW: server BW = {0}", m_nServerBW));
        }

        void HandleClientBW(RTMPPacket packet)
        {
            m_nClientBW = Coding.ReadInt32(packet.m_body, 0);
            if (packet.m_nBodySize > 4)
                m_nClientBW2 = packet.m_body[4];
            else
                m_nClientBW2 = 0;
            logger.Log(string.Format("HandleClientBW: client BW = {0} {1}", m_nClientBW, m_nClientBW2));
        }

        void HandleSharedObject(RTMPPacket packet)
        {
            string name = Coding.ReadString(packet.m_body, 0);
            if (!SOList.ContainsKey(name))
                return;
            SharedObject so = SOList[name];
            int offset = name.Length + 2 + 12;
            byte[] data = new byte[packet.m_body.Length - offset];
            Array.Copy(packet.m_body, offset, data, 0, data.Length);
            so.handleEvents(data);
        }

        /// <summary>
        /// Analyzes and responds if required to the given <see cref="RTMPPacket"/>.
        /// </summary>
        /// <param name="packet">The <see cref="RTMPPacket"/> to inspect amnd react to.</param>
        /// <returns>0 (false) for OK/Failed/error, 1 for 'Stop or Complete' (true)</returns>	        
        void handleInvokeResult(AMFObject obj, invokePacket invoked)
        {
            if (invoked == null)
            {
                logger.Log("Method not found in pending list!");
                return;
            }
            else
                logger.Log(string.Format("method: <{0}>", invoked.method));
            string method = invoked.method;
            switch (method)
            {
                case "connect":
                    string code = obj.GetProperty(3).GetObject().GetProperty("code").GetString();
                    if (!string.IsNullOrEmpty(link.token))
                    {
                        List<AMFObjectProperty> props = new List<AMFObjectProperty>();
                        obj.FindMatchingProperty("secureToken", props, int.MaxValue);
                        if (props.Count > 0)
                        {
                            string decodedToken = Tea.Decrypt(props[0].GetString(), link.token);
                            SendSecureTokenResponse(decodedToken);
                        }
                    }
                    SendServerBW();
                    /* if (!string.IsNullOrEmpty(link.subscribepath)) SendFCSubscribe(link.subscribepath);
                     else if (link.bLiveStream) SendFCSubscribe(link.playpath);*/
                    IsConnected = true;
                    onStatus(obj.m_properties[3].ToObject());
                    break;
                case "createStream":
                    if (NSPendings.Count == 0)
                        break;
                    NetStream strm = NSPendings.Values.ToArray()[0];
                    strm.ID = (int)obj.GetProperty(3).GetNumber();
                    NSList.Add(strm.ID, strm);
                    NSPendings.Remove(strm.name);
                    SendPlay(strm);
                    SendPing(3, strm.ID, (uint)m_nBufferMS);
                    break;
                case "play":
                    break;
                default:
                    AMFObjectProperty result = new AMFObjectProperty();
                    if (obj.m_properties.Count <= 3)
                        result.m_type = AMFDataType.AMF_NULL;
                    else
                        result = obj.m_properties[3];
                    if (invoked.responder != null)
                        invoked.responder(result.ToObject());
                    else
                        onResult(method, result.ToObject());
                    break;
            }
        }
        void handleInvokeStatus(AMFObject obj)
        {
            string code = obj.GetProperty(3).GetObject().GetProperty("code").GetString();
            string level = obj.GetProperty(3).GetObject().GetProperty("level").GetString();
            logger.Log(string.Format("onStatus: code :{0}, level: {1}", code, level));
            /*            List<object>list=new List<object>();
                        for(int i=2;i<obj.m_properties.Count;i++)
                            list.Add(obj.m_properties[i].ToObject());*/
            switch (code)
            {
                case "NetConnection.Connect.Close":
                case "NetConnection.Connect.InvalidApp":
                    Close();
                    break;
                case "NetStream.Failed":
                case "NetStream.Play.Failed":
                case "NetStream.Play.Reset":
                case "NetStream.Play.StreamNotFound":
                case "NetStream.Play.Start":
                case "NetStream.Publish.Start":
                case "NetStream.Play.Complete":
                case "NetStream.Play.Stop":
                case "NetStream.Pause.Notify":
                    string name = obj.GetProperty(3).GetObject().GetProperty("details").GetString();
                    if (obj.m_properties.Count < 3)
                        break;
                    foreach (NetStream ns in NSList.Values)
                    {
                        if (ns.name == name)
                        {
                            ns.handleStatus(obj.m_properties[3].ToObject());
                            break;
                        }
                    }
                    break;
                default:
                    break;
            }
            if (obj.m_properties.Count < 3)
                return;
            onStatus(obj.m_properties[3].ToObject());
        }
        bool HandleInvoke(RTMPPacket packet)
        {

            if (packet.m_body[0] != 0x02) // make sure it is a string method name we start with
            {
                logger.Log("HandleInvoke: Sanity failed. no string method in invoke packet");
                return false;
            }
            string name = Coding.ReadString(packet.m_body, 1);
            AMFObject obj = new AMFObject();
            int nRes = obj.Decode(packet.m_body, 0, (int)packet.m_nBodySize, false);
            if (nRes < 0)
            {
                logger.Log("HandleInvoke: error decoding invoke packet");
                return false;
            }

            obj.Dump();
            string method = obj.GetProperty(0).GetString();
            double txn = obj.GetProperty(1).GetNumber();

            logger.Log(string.Format("server invoking <{0}>", method));
            switch (method)
            {
                case "_result":
                    //invokePacket invoked = m_methodCalls.Dequeue();
                    handleInvokeResult(obj, m_methodCalls[(int)txn]);
                    m_methodCalls.Remove((int)txn);
                    break;
                case "onBWDone":
                    if (m_nBWCheckCounter == 0)
                        SendCheckBW();
                    break;
                case "_onbwcheck":
                    SendCheckBWResult(txn);
                    break;
                case "_onbwdone":
                    //invokePacket[] queue = m_methodCalls.ToArray(();
                    // m_methodCalls.Clear();
                    // for (int i = 0; i < queue.Length; i++) if (queue[i].method != "_checkbw") m_methodCalls.Enqueue(queue[i]);
                    foreach (int key in m_methodCalls.Keys)
                    {
                        if (m_methodCalls[key].method == "_checkbw")
                            m_methodCalls.Remove(key);
                    }
                    break;
                case "_error":
                    logger.Log("rtmp server sent error");
                    break;
                case "close":
                    logger.Log("rtmp server requested close");
                    Close();
                    break;
                case "onStatus":
                    handleInvokeStatus(obj);
                    break;
                default:
                    handleInvokeMethod(method, obj);
                    break;
            }
            return true;
        }
        void handleInvokeMethod(string methodname, AMFObject obj)
        {
            if (client == null)
                return;
            try
            {
                Type t = client.GetType();
                if (t == null)
                    return;
                MethodInfo method = t.GetMethod(methodname);
                if (method == null)
                    return;
                if (obj.m_properties.Count > 3)
                {
                    List<object> paramlist = new List<object>();
                    for (int i = 3; i < obj.m_properties.Count; i++)
                        paramlist.Add(obj.GetProperty(i).ToObject());
                    method.Invoke(client, paramlist.ToArray());
                }
                else
                    method.Invoke(client, null);

            }
            catch (Exception ex)
            {
                logger.Log("Fatal error whilst invoking method by server: " + ex.Message);
            }
        }

        void HandleMetadata(RTMPPacket p)
        {
            if (p.m_body[0] == 2)
            {
                ushort size=Coding.ReadInt16(p.m_body, 1);
                string name=Coding.ReadString(p.m_body, 1);
            }
        }
        #endregion


        private bool ReadPacket(out RTMPPacket packet, byte type)
        {
            string strError = "";
            packet = new RTMPPacket();
            if (m_socket == null || !m_socket.bConnected)
                return false;

            try
            {

                // Chunk Basic Header (1, 2 or 3 bytes)
                // the two most significant bits hold the chunk type
                // value in the 6 least significant bits gives the chunk stream id (0,1,2 are reserved): 0 -> 3 byte header | 1 -> 2 byte header | 2 -> low level protocol message | 3-63 -> stream id
                byte[] singleByteToReadBuffer = new byte[1];
                /*if (ReadN(singleByteToReadBuffer, 0, 1) != 1)
                {
                    logger.Log("failed to read RTMP packet header");
                    packet = null;
                    return false;
                }

                byte type = singleByteToReadBuffer[0];*/
                byte headerType = (byte)((type & 0xc0) >> 6);
                int channel = (byte)(type & 0x3f);
                singleByteToReadBuffer[0] = type;
                if (channel == 0)
                {
                    if (ReadN(singleByteToReadBuffer, 0, 1) != 1)
                    {
                        if (m_socket == null || !m_socket.bConnected)
                            return false;
                        logger.Log("failed to read RTMP packet header 2nd byte");
                        packet = null;
                        return false;
                    }
                    channel = singleByteToReadBuffer[0];
                    channel += 64;
                    //header++;
                }
                else if (channel == 1)
                {
                    int tmp;
                    byte[] hbuf = new byte[2];

                    if (ReadN(hbuf, 0, 2) != 2)
                    {
                        if (m_socket == null || !m_socket.bConnected)
                            return false;
                        logger.Log("failed to read RTMP packet header 3rd and 4th byte");
                        packet = null;
                        return false;
                    }
                    tmp = ((hbuf[1]) << 8) + hbuf[0];
                    channel = tmp + 64;
                    logger.Log(string.Format("channel: {0}", channel));
                    //header += 2;
                }

                uint nSize = Coding.packetSize[headerType];

                //logger.Log(string.Format("reading RTMP packet chunk on channel {0}, headersz {1}", channel, nSize));

                if (nSize < Coding.RTMP_LARGE_HEADER_SIZE && m_vecChannelsIn[channel] != null)
                    packet = m_vecChannelsIn[channel]; // using values from the last message of this channel
                else
                    packet = new RTMPPacket() { HeaderType = (HeaderType)headerType, m_nChannel = channel, m_hasAbsTimestamp = true }; // new packet

                nSize--;

                byte[] header = new byte[Coding.RTMP_LARGE_HEADER_SIZE];
                if (nSize > 0 && ReadN(header, 0, (int)nSize) != nSize)
                {
                    if (m_socket == null || !m_socket.bConnected)
                        return false;
                    logger.Log(string.Format("failed to read RTMP packet header. type: {0}", type));
                    return false;
                }

                if (nSize >= 3)
                {
                    try
                    {
                        packet.m_nTimeStamp = (uint)Coding.ReadInt24(header, 0);
                    }
                    catch
                    {
                        packet.m_nTimeStamp = 0;
                    }
                    if (nSize >= 6)
                    {
                        packet.m_nBodySize = (uint)Coding.ReadInt24(header, 3);
                        //logger.Log(string.Format("new packet body to read {0}", packet.m_nBodySize));
                        packet.m_nBytesRead = 0;
                        packet.Free(); // new packet body

                        if (nSize > 6)
                        {
                            if (Enum.IsDefined(typeof(PacketType), header[6])) packet.PacketType = (PacketType)header[6];
                            else logger.Log(string.Format("Unknown packet type received: {0}", header[6]));

                            if (nSize == 11)
                                packet.m_nInfoField2 = Coding.ReadInt32LE(header, 7);
                        }
                    }

                    if (packet.m_nTimeStamp == 0xffffff)
                    {
                        byte[] extendedTimestampDate = new byte[4];
                        if (ReadN(extendedTimestampDate, 0, 4) != 4)
                        {
                            if (m_socket == null || !m_socket.bConnected)
                                return false;
                            logger.Log("failed to read extended timestamp");
                            return false;
                        }
                        packet.m_nTimeStamp = (uint)Coding.ReadInt32(extendedTimestampDate, 0);
                    }
                }

                if (packet.m_nBodySize >= 0 && packet.m_body == null && !packet.AllocPacket((int)packet.m_nBodySize))
                {
                    //CLog::Log(LOGDEBUG,"%s, failed to allocate packet", __FUNCTION__);
                    return false;
                }

                uint nToRead = packet.m_nBodySize - packet.m_nBytesRead;
                uint nChunk = (uint)InChunkSize;
                if (nToRead < nChunk)
                    nChunk = nToRead;

                int read = ReadN(packet.m_body, (int)packet.m_nBytesRead, (int)nChunk);
                if (read == -1)
                    return false;
                if (read != nChunk)
                {
                    logger.Log(string.Format("failed to read RTMP packet body. total:{0}/{1} chunk:{2}/{3}", packet.m_nBytesRead, packet.m_nBodySize, read, nChunk));
                    packet.m_body = null; // we dont want it deleted since its pointed to from the stored packets (m_vecChannelsIn)
                    return false;
                }

                packet.m_nBytesRead += nChunk;

                // keep the packet as ref for other packets on this channel
                m_vecChannelsIn[packet.m_nChannel] = packet.ShallowCopy();

                if (packet.IsReady())
                {
                    //logger.Log(string.Format("packet with {0} bytes read", packet.m_nBytesRead));

                    // make packet's timestamp absolute 
                    if (!packet.m_hasAbsTimestamp)
                        packet.m_nTimeStamp += m_channelTimestamp[packet.m_nChannel]; // timestamps seem to be always relative!! 
                    m_channelTimestamp[packet.m_nChannel] = packet.m_nTimeStamp;

                    // reset the data from the stored packet. we keep the header since we may use it later if a new packet for this channel
                    // arrives and requests to re-use some info (small packet header)
                    m_vecChannelsIn[packet.m_nChannel].m_body = null;
                    m_vecChannelsIn[packet.m_nChannel].m_nBytesRead = 0;
                    m_vecChannelsIn[packet.m_nChannel].m_hasAbsTimestamp = false; // can only be false if we reuse header
                }
            }
            catch (Exception ex)
            {
                strError = ex.Message;
            }
            return true;
        }

        bool HandShake(bool FP9HandShake)
        {
            int offalg = 0;

            bool encrypted = link.protocol == Protocol.RTMPE || link.protocol == Protocol.RTMPTE;

            if (encrypted && !FP9HandShake)
            {
                logger.Log("RTMPE requires FP9 handshake!");
                return false;
            }

            byte[] clientsig = new byte[Coding.RTMP_SIG_SIZE + 1];
            byte[] serversig = new byte[Coding.RTMP_SIG_SIZE];

            if (encrypted)
            {
                clientsig[0] = 0x06; // 0x08 is RTMPE as well
                offalg = 1;
            }
            else clientsig[0] = 0x03;

            int uptime = System.Environment.TickCount;
            byte[] uptime_bytes = BitConverter.GetBytes(System.Net.IPAddress.HostToNetworkOrder(uptime));
            Array.Copy(uptime_bytes, 0, clientsig, 1, uptime_bytes.Length);

            if (FP9HandShake)
            {
                /* set version to at least 9.0.115.0 */
                if (encrypted)
                {
                    /*clientsig[5] = 128;
                    clientsig[7] = 3;*/
                    clientsig[5] = 9;
                    clientsig[7] = 0x7c;
                }
                else
                {
                    clientsig[5] = 10;
                    clientsig[7] = 45;
                }
                clientsig[6] = 0;
                clientsig[8] = 2;

                logger.Log(string.Format("Client type: {0}", clientsig[0]));
            }
            else
            {
                clientsig[5] = 0; clientsig[6] = 0; clientsig[7] = 0; clientsig[8] = 0;
            }

            // generate random data
            Random rand = new Random();
            for (int i = 9; i <= Coding.RTMP_SIG_SIZE; i++) clientsig[i] = (byte)rand.Next(0, 256);

            int dhposClient = 0;
            byte[] keyIn = null;
            byte[] keyOut = null;

            if (encrypted)
            {
                // generate Diffie-Hellmann parameters
                Org.BouncyCastle.Math.BigInteger p = new Org.BouncyCastle.Math.BigInteger(1, DH_MODULUS_BYTES);
                Org.BouncyCastle.Math.BigInteger g = Org.BouncyCastle.Math.BigInteger.ValueOf(2);
                Org.BouncyCastle.Crypto.Parameters.DHParameters dhParams = new Org.BouncyCastle.Crypto.Parameters.DHParameters(p, g);
                Org.BouncyCastle.Crypto.Parameters.DHKeyGenerationParameters keySpec = new Org.BouncyCastle.Crypto.Parameters.DHKeyGenerationParameters(new Org.BouncyCastle.Security.SecureRandom(), dhParams);
                Org.BouncyCastle.Crypto.Generators.DHBasicKeyPairGenerator keyGen = new Org.BouncyCastle.Crypto.Generators.DHBasicKeyPairGenerator();
                keyGen.Init(keySpec);
                Org.BouncyCastle.Crypto.AsymmetricCipherKeyPair pair = keyGen.GenerateKeyPair();
                keyAgreement = new Org.BouncyCastle.Crypto.Agreement.DHBasicAgreement();
                keyAgreement.Init(pair.Private);

                byte[] publicKey = (pair.Public as Org.BouncyCastle.Crypto.Parameters.DHPublicKeyParameters).Y.ToByteArray();

                byte[] temp = new byte[128];
                if (publicKey.Length < 128)
                {
                    Array.Copy(publicKey, 0, temp, 128 - publicKey.Length, publicKey.Length);
                    publicKey = temp;
                    logger.Log("padded public key length to 128");
                }
                else if (publicKey.Length > 128)
                {
                    Array.Copy(publicKey, publicKey.Length - 128, temp, 0, 128);
                    publicKey = temp;
                    logger.Log("truncated public key length to 128");
                }

                dhposClient = (int)GetDHOffset(offalg, clientsig, 1, Coding.RTMP_SIG_SIZE);
                logger.Log(string.Format("DH pubkey position: {0}", dhposClient));

                Array.Copy(publicKey, 0, clientsig, 1 + dhposClient, 128);
            }

            // set handshake digest
            if (FP9HandShake)
            {
                int digestPosClient = (int)GetDigestOffset(offalg, clientsig, 1, Coding.RTMP_SIG_SIZE); // maybe reuse this value in verification
                logger.Log(string.Format("Client digest offset: {0}", digestPosClient));

                CalculateDigest(digestPosClient, clientsig, 1, Coding.GenuineFPKey, 30, clientsig, 1 + digestPosClient);

                logger.Log("Initial client digest: ");
                string digestAsHexString = "";
                for (int i = 1 + digestPosClient; i < 1 + digestPosClient + Coding.SHA256_DIGEST_LENGTH; i++) digestAsHexString += clientsig[i].ToString("X2") + " ";
                logger.Log(digestAsHexString);
            }

            WriteN(clientsig, 0, Coding.RTMP_SIG_SIZE + 1);

            byte[] singleByteToReadBuffer = new byte[1];
            if (ReadN(singleByteToReadBuffer, 0, 1) != 1) return false;
            byte type = singleByteToReadBuffer[0]; // 0x03 or 0x06

            logger.Log(string.Format("Type Answer   : {0}", type.ToString()));

            if (type != clientsig[0]) logger.Log(string.Format("Type mismatch: client sent {0}, server answered {1}", clientsig[0], type));

            if (ReadN(serversig, 0, Coding.RTMP_SIG_SIZE) != Coding.RTMP_SIG_SIZE) return false;
            uint suptime = 0;
            try
            {
                // decode server response
                suptime = (uint)Coding.ReadInt32(serversig, 0);
            }
            catch { }
            logger.Log(string.Format("Server Uptime : {0}", suptime));
            logger.Log(string.Format("FMS Version   : {0}.{1}.{2}.{3}", serversig[4], serversig[5], serversig[6], serversig[7]));

            if (FP9HandShake && type == 3 && serversig[4] == 0) FP9HandShake = false; //??????????????????????

            byte[] clientResp;

            if (FP9HandShake)
            {
                // we have to use this signature now to find the correct algorithms for getting the digest and DH positions
                int digestPosServer = (int)GetDigestOffset(offalg, serversig, 0, Coding.RTMP_SIG_SIZE);
                int dhposServer = (int)GetDHOffset(offalg, serversig, 0, Coding.RTMP_SIG_SIZE);

                if (!VerifyDigest(digestPosServer, serversig, Coding.GenuineFMSKey, 36))
                {
                    logger.Log("Trying different position for server digest!");
                    offalg ^= 1;
                    digestPosServer = (int)GetDigestOffset(offalg, serversig, 0, Coding.RTMP_SIG_SIZE);
                    dhposServer = (int)GetDHOffset(offalg, serversig, 0, Coding.RTMP_SIG_SIZE);

                    if (!VerifyDigest(digestPosServer, serversig, Coding.GenuineFMSKey, 36))
                    {
                        logger.Log("Couldn't verify the server digest");//,  continuing anyway, will probably fail!\n");
                        return false;
                    }
                }

                logger.Log(string.Format("Server DH public key offset: {0}", dhposServer));

                // generate SWFVerification token (SHA256 HMAC hash of decompressed SWF, key are the last 32 bytes of the server handshake)            
                if (link.SWFHash != null)
                {
                    byte[] swfVerify = new byte[2] { 0x01, 0x01 };
                    Array.Copy(swfVerify, link.SWFVerificationResponse, 2);
                    List<byte> data = new List<byte>();
                    Coding.EncodeInt32(data, link.SWFSize);
                    Coding.EncodeInt32(data, link.SWFSize);
                    Array.Copy(data.ToArray(), 0, link.SWFVerificationResponse, 2, data.Count);
                    byte[] key = new byte[Coding.SHA256_DIGEST_LENGTH];
                    Array.Copy(serversig, Coding.RTMP_SIG_SIZE - Coding.SHA256_DIGEST_LENGTH, key, 0, Coding.SHA256_DIGEST_LENGTH);
                    HMACsha256(link.SWFHash, 0, Coding.SHA256_DIGEST_LENGTH, key, Coding.SHA256_DIGEST_LENGTH, link.SWFVerificationResponse, 10);
                }

                // do Diffie-Hellmann Key exchange for encrypted RTMP
                if (encrypted)
                {
                    // compute secret key	
                    byte[] secretKey = new byte[128];

                    byte[] serverKey = new byte[128];
                    Array.Copy(serversig, dhposServer, serverKey, 0, 128);

                    Org.BouncyCastle.Crypto.Parameters.DHParameters dhParams =
                        new Org.BouncyCastle.Crypto.Parameters.DHParameters(
                            new Org.BouncyCastle.Math.BigInteger(1, DH_MODULUS_BYTES),
                            Org.BouncyCastle.Math.BigInteger.ValueOf(2));

                    Org.BouncyCastle.Crypto.Parameters.DHPublicKeyParameters dhPubKey =
                        new Org.BouncyCastle.Crypto.Parameters.DHPublicKeyParameters(
                            new Org.BouncyCastle.Math.BigInteger(1, serverKey),
                            dhParams);

                    secretKey = keyAgreement.CalculateAgreement(dhPubKey).ToByteArray();

                    logger.Log("DH SecretKey:");
                    logger.LogHex(secretKey, 0, 128);

                    InitRC4Encryption(
                        secretKey,
                        serversig, dhposServer,
                        clientsig, 1 + dhposClient,
                        out keyIn, out keyOut);
                }

                clientResp = new byte[Coding.RTMP_SIG_SIZE];
                for (int i = 0; i < Coding.RTMP_SIG_SIZE; i++) clientResp[i] = (byte)(rand.Next(0, 256));

                // calculate response now
                byte[] signatureResp = new byte[Coding.SHA256_DIGEST_LENGTH];
                byte[] digestResp = new byte[Coding.SHA256_DIGEST_LENGTH];

                HMACsha256(serversig, digestPosServer, Coding.SHA256_DIGEST_LENGTH, Coding.GenuineFPKey, Coding.GenuineFPKey.Length, digestResp, 0);
                HMACsha256(clientResp, 0, Coding.RTMP_SIG_SIZE - Coding.SHA256_DIGEST_LENGTH, digestResp, Coding.SHA256_DIGEST_LENGTH, signatureResp, 0);

                // some info output
                logger.Log("Calculated digest key from secure key and server digest: ");
                logger.LogHex(digestResp, 0, Coding.SHA256_DIGEST_LENGTH);

                // FP10 stuff
                if (type == 8)
                {
                    /* encrypt signatureResp */
                    for (int i = 0; i < Coding.SHA256_DIGEST_LENGTH; i += 8)
                        rtmpe8_sig(signatureResp, i, digestResp[i] % 15);
                }

                logger.Log("Client signature calculated:");
                logger.LogHex(signatureResp, 0, Coding.SHA256_DIGEST_LENGTH);

                Array.Copy(signatureResp, 0, clientResp, Coding.RTMP_SIG_SIZE - Coding.SHA256_DIGEST_LENGTH, Coding.SHA256_DIGEST_LENGTH);
            }
            else
            {
                clientResp = serversig;
            }

            WriteN(clientResp, 0, Coding.RTMP_SIG_SIZE);

            // 2nd part of handshake
            byte[] resp = new byte[Coding.RTMP_SIG_SIZE];
            if (ReadN(resp, 0, Coding.RTMP_SIG_SIZE) != Coding.RTMP_SIG_SIZE) return false;

            if (FP9HandShake)
            {
                bool bSignatureFound = true;
                int digestPosClient = 0;
                byte[] signature = null;
                byte[] digest = null;
                if (resp[4] == 0 && resp[5] == 0 && resp[6] == 0 && resp[7] == 0)
                {
                    logger.Log("Wait, did the server just refuse signed authentication?");
                }

                // verify server response
                //offalg ^= 1;
                digestPosClient = (int)GetDigestOffset(offalg, clientsig, 1, Coding.RTMP_SIG_SIZE);

                signature = new byte[Coding.SHA256_DIGEST_LENGTH];
                digest = new byte[Coding.SHA256_DIGEST_LENGTH];

                logger.Log(string.Format("Client signature digest position: {0}", digestPosClient));
                HMACsha256(clientsig, 1 + digestPosClient, Coding.SHA256_DIGEST_LENGTH, Coding.GenuineFMSKey, Coding.GenuineFMSKey.Length, digest, 0);
                HMACsha256(resp, 0, Coding.RTMP_SIG_SIZE - Coding.SHA256_DIGEST_LENGTH, digest, Coding.SHA256_DIGEST_LENGTH, signature, 0);

                // show some information
                logger.Log("Digest key: ");
                logger.LogHex(digest, 0, Coding.SHA256_DIGEST_LENGTH);

                // FP10 stuff
                if (type == 8)
                {
                    /* encrypt signatureResp */
                    for (int i = 0; i < Coding.SHA256_DIGEST_LENGTH; i += 8)
                        rtmpe8_sig(signature, i, digest[i] % 15);
                }

                logger.Log("Signature calculated:");
                logger.LogHex(signature, 0, Coding.SHA256_DIGEST_LENGTH);

                logger.Log("Server sent signature:");
                logger.LogHex(resp, Coding.RTMP_SIG_SIZE - Coding.SHA256_DIGEST_LENGTH, Coding.SHA256_DIGEST_LENGTH);

                for (int i = 0; i < Coding.SHA256_DIGEST_LENGTH; i++)
                {
                    if (signature[i] != resp[Coding.RTMP_SIG_SIZE - Coding.SHA256_DIGEST_LENGTH + i])
                    {
                        logger.Log("Signature not found, let's try changing the alg(message format2)!");
                        bSignatureFound = false;
                        break;
                    }
                }


                if (!bSignatureFound)
                {
                    offalg ^= 1;
                    digestPosClient = (int)GetDigestOffset(offalg, clientsig, 1, Coding.RTMP_SIG_SIZE);

                    signature = new byte[Coding.SHA256_DIGEST_LENGTH];
                    digest = new byte[Coding.SHA256_DIGEST_LENGTH];

                    logger.Log(string.Format("Client signature digest position: {0}", digestPosClient));
                    HMACsha256(clientsig, 1 + digestPosClient, Coding.SHA256_DIGEST_LENGTH, Coding.GenuineFMSKey, Coding.GenuineFMSKey.Length, digest, 0);
                    HMACsha256(resp, 0, Coding.RTMP_SIG_SIZE - Coding.SHA256_DIGEST_LENGTH, digest, Coding.SHA256_DIGEST_LENGTH, signature, 0);

                    // show some information
                    logger.Log("Digest key: ");
                    logger.LogHex(digest, 0, Coding.SHA256_DIGEST_LENGTH);

                    // FP10 stuff
                    if (type == 8)
                    {
                        /* encrypt signatureResp */
                        for (int i = 0; i < Coding.SHA256_DIGEST_LENGTH; i += 8)
                            rtmpe8_sig(signature, i, digest[i] % 15);
                    }

                    logger.Log("Signature calculated:");
                    logger.LogHex(signature, 0, Coding.SHA256_DIGEST_LENGTH);

                    logger.Log("Server sent signature:");
                    logger.LogHex(resp, Coding.RTMP_SIG_SIZE - Coding.SHA256_DIGEST_LENGTH, Coding.SHA256_DIGEST_LENGTH);

                    for (int i = 0; i < Coding.SHA256_DIGEST_LENGTH; i++)
                    {
                        byte b = resp[Coding.RTMP_SIG_SIZE - Coding.SHA256_DIGEST_LENGTH + i];
                        if (signature[i] != resp[Coding.RTMP_SIG_SIZE - Coding.SHA256_DIGEST_LENGTH + i])
                        {
                            logger.Log("Server not genuine Adobe!");
                            return false;
                        }
                    }
                }



                logger.Log("Genuine Adobe Flash Media Server");

                if (encrypted)
                {
                    // set keys for encryption from now on
                    rc4In = new Org.BouncyCastle.Crypto.Engines.RC4Engine();
                    rc4In.Init(false, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(keyIn));

                    rc4Out = new Org.BouncyCastle.Crypto.Engines.RC4Engine();
                    rc4Out.Init(true, new Org.BouncyCastle.Crypto.Parameters.KeyParameter(keyOut));

                    // update 'encoder / decoder state' for the RC4 keys
                    // both parties *pretend* as if handshake part 2 (1536 bytes) was encrypted
                    // effectively this hides / discards the first few bytes of encrypted session
                    // which is known to increase the secure-ness of RC4
                    // RC4 state is just a function of number of bytes processed so far
                    // that's why we just run 1536 arbitrary bytes through the keys below
                    byte[] dummyBytes = new byte[Coding.RTMP_SIG_SIZE];
                    rc4In.ProcessBytes(dummyBytes, 0, Coding.RTMP_SIG_SIZE, new byte[Coding.RTMP_SIG_SIZE], 0);
                    rc4Out.ProcessBytes(dummyBytes, 0, Coding.RTMP_SIG_SIZE, new byte[Coding.RTMP_SIG_SIZE], 0);
                }
            }
            else
            {
                for (int i = 0; i < Coding.RTMP_SIG_SIZE; i++)
                    if (resp[i] != clientsig[i + 1])
                    {
                        logger.Log("client signature does not match!");
                        return false;
                    }
            }

            logger.Log("Handshaking finished....");
            return true;
        }
        uint GetDHOffset(int alg, byte[] handshake, int bufferoffset, uint len)
        {
            if (alg == 0) return GetDHOffset1(handshake, bufferoffset, len);
            else return GetDHOffset2(handshake, bufferoffset, len);
        }

        uint GetDigestOffset(int alg, byte[] handshake, int bufferoffset, uint len)
        {
            if (alg == 0) return GetDigestOffset1(handshake, bufferoffset, len);
            else return GetDigestOffset2(handshake, bufferoffset, len);
        }

        uint GetDHOffset1(byte[] handshake, int bufferoffset, uint len)
        {
            int offset = 0;
            bufferoffset += 1532;

            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset];// (*ptr);

            int res = (offset % 632) + 772;

            if (res + 128 > 1531)
            {
                logger.Log(string.Format("Couldn't calculate DH offset (got {0}), exiting!", res));
                throw new Exception();
            }

            return (uint)res;
        }

        uint GetDigestOffset1(byte[] handshake, int bufferoffset, uint len)
        {
            int offset = 0;
            bufferoffset += 8;

            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset];

            int res = (offset % 728) + 12;

            if (res + 32 > 771)
            {
                logger.Log(string.Format("Couldn't calculate digest offset (got {0}), exiting!", res));
                throw new Exception();
            }

            return (uint)res;
        }

        uint GetDHOffset2(byte[] handshake, int bufferoffset, uint len)
        {
            uint offset = 0;
            bufferoffset += 768;
            //assert(RTMP_SIG_SIZE <= len);

            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset];

            uint res = (offset % 632) + 8;

            if (res + 128 > 767)
            {
                logger.Log(string.Format("Couldn't calculate correct DH offset (got {0}), exiting!", res));
                throw new Exception();
            }
            return res;
        }

        uint GetDigestOffset2(byte[] handshake, int bufferoffset, uint len)
        {
            uint offset = 0;
            bufferoffset += 772;
            //assert(12 <= len);

            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset]; bufferoffset++;
            offset += handshake[bufferoffset];// (*ptr);

            uint res = (offset % 728) + 776;

            if (res + 32 > 1535)
            {
                logger.Log(string.Format("Couldn't calculate correct digest offset (got {0}), exiting", res));
                throw new Exception();
            }
            return res;
        }

        void CalculateDigest(int digestPos, byte[] handshakeMessage, int handshakeOffset, byte[] key, int keyLen, byte[] digest, int digestOffset)
        {
            const int messageLen = Coding.RTMP_SIG_SIZE - Coding.SHA256_DIGEST_LENGTH;
            byte[] message = new byte[messageLen];

            Array.Copy(handshakeMessage, handshakeOffset, message, 0, digestPos);
            Array.Copy(handshakeMessage, handshakeOffset + digestPos + Coding.SHA256_DIGEST_LENGTH, message, digestPos, messageLen - digestPos);

            HMACsha256(message, 0, messageLen, key, keyLen, digest, digestOffset);
        }

        bool VerifyDigest(int digestPos, byte[] handshakeMessage, byte[] key, int keyLen)
        {
            byte[] calcDigest = new byte[Coding.SHA256_DIGEST_LENGTH];

            CalculateDigest(digestPos, handshakeMessage, 0, key, keyLen, calcDigest, 0);

            for (int i = 0; i < Coding.SHA256_DIGEST_LENGTH; i++)
            {
                if (handshakeMessage[digestPos + i] != calcDigest[i]) return false;
            }
            return true;
        }

        void HMACsha256(byte[] message, int messageOffset, int messageLen, byte[] key, int keylen, byte[] digest, int digestOffset)
        {
            System.Security.Cryptography.HMAC hmac = System.Security.Cryptography.HMACSHA256.Create("HMACSHA256");
            byte[] actualKey = new byte[keylen]; Array.Copy(key, actualKey, keylen);
            hmac.Key = actualKey;

            byte[] actualMessage = new byte[messageLen];
            Array.Copy(message, messageOffset, actualMessage, 0, messageLen);

            byte[] calcDigest = hmac.ComputeHash(actualMessage);
            Array.Copy(calcDigest, 0, digest, digestOffset, calcDigest.Length);
        }

        void InitRC4Encryption(byte[] secretKey, byte[] pubKeyIn, int inOffset, byte[] pubKeyOut, int outOffset, out byte[] rc4keyIn, out byte[] rc4keyOut)
        {
            byte[] digest = new byte[Coding.SHA256_DIGEST_LENGTH];

            System.Security.Cryptography.HMAC hmac = System.Security.Cryptography.HMACSHA256.Create("HMACSHA256");
            hmac.Key = secretKey;

            byte[] actualpubKeyIn = new byte[128];
            Array.Copy(pubKeyIn, inOffset, actualpubKeyIn, 0, 128);
            digest = hmac.ComputeHash(actualpubKeyIn);

            rc4keyOut = new byte[16];
            Array.Copy(digest, rc4keyOut, 16);
            logger.Log("RC4 Out Key: ");
            logger.LogHex(rc4keyOut, 0, 16);

            hmac = System.Security.Cryptography.HMACSHA256.Create("HMACSHA256");
            hmac.Key = secretKey;

            byte[] actualpubKeyOut = new byte[128];
            Array.Copy(pubKeyOut, outOffset, actualpubKeyOut, 0, 128);
            digest = hmac.ComputeHash(actualpubKeyOut);

            rc4keyIn = new byte[16];
            Array.Copy(digest, rc4keyIn, 16);
            logger.Log("RC4 In Key: ");
            logger.LogHex(rc4keyIn, 0, 16);
        }

        /// <summary>
        /// RTMPE type 8 uses XTEA on the regular signature (http://en.wikipedia.org/wiki/XTEA)
        /// </summary>
        static void rtmpe8_sig(byte[] array, int offset, int keyid)
        {
            uint i, num_rounds = 32;
            uint v0, v1, sum = 0, delta = 0x9E3779B9;
            uint[] k;

            v0 = BitConverter.ToUInt32(array, offset);
            v1 = BitConverter.ToUInt32(array, offset + 4);

            k = Coding.rtmpe8_keys[keyid];

            for (i = 0; i < num_rounds; i++)
            {
                v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + k[sum & 3]);
                sum += delta;
                v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + k[(sum >> 11) & 3]);
            }

            Array.Copy(BitConverter.GetBytes(v0), 0, array, offset, 4);
            Array.Copy(BitConverter.GetBytes(v1), 0, array, offset + 4, 4);
        }
        int ReadN(byte[] buffer, int offset, int size)
        {
            // keep reading until wanted amount has been received or timeout after nothing has been received is elapsed
            if (m_socket == null || !m_socket.bConnected)
                return -1;
            byte[] data = new byte[size];
            int readThisRun = 0;
            int i = receiveTimeoutMS / 100;
            while (readThisRun < size)
            {
                int read = m_socket.Receive(ref data, readThisRun, size - readThisRun, 5000);
                if (read == -1)
                    return read;
                // decrypt if needed
                if (read > 0)
                {
                    if (rc4In != null)
                    {
                        rc4In.ProcessBytes(data, readThisRun, read, buffer, offset + readThisRun);
                    }
                    else
                    {
                        Array.Copy(data, readThisRun, buffer, offset + readThisRun, read);
                    }

                    readThisRun += read;

                    bytesReadTotal += read;

                    if (bytesReadTotal > lastSentBytesRead + (m_nClientBW / 2)) SendBytesReceived(); // report bytes read

                    i = receiveTimeoutMS / 100; // we just got some data, reset the receive timeout
                }
                else
                {
                    i--;
                    System.Threading.Thread.Sleep(100);
                    if (i <= 0) return readThisRun;
                }
            }

            return readThisRun;
        }


        void WriteN(byte[] buffer, int offset, int size)
        {
            // encrypt if needed
            if (m_socket == null || !m_socket.bConnected)
                return;
            if (rc4Out != null)
            {
                byte[] result = new byte[size];
                rc4Out.ProcessBytes(buffer, offset, size, result, 0);
                m_socket.Send(result, 5000);
            }
            else
            {
                m_socket.Send(buffer, offset, size, 5000);
            }
        }
        private void onStatus(object props)
        {
            if (Status != null)
                Status(props);
        }
        private void onResult(string method, object result)
        {
            if (Result != null)
                Result(method, result);
        }
        private class invokePacket
        {
            public string method;
            public ResponseHandler responder = null;
            public invokePacket(string method, ResponseHandler responder = null)
            {
                this.method = method;
                this.responder = responder;
            }
        }
    }
}
